package net.ncdesign.components.nodeWorkspace.core.display
{
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.utils.Dictionary;
	
	import mx.collections.ArrayCollection;
	import mx.containers.Canvas;
	import mx.core.ScrollPolicy;
	import mx.core.UIComponent;
	import mx.events.ScrollEvent;
	import mx.styles.CSSStyleDeclaration;
	import mx.styles.StyleManager;
	
	import net.ncdesign.components.nodeWorkspace.core.base.LinkControlPoint;
	import net.ncdesign.components.nodeWorkspace.core.base.NodeLink;
	import net.ncdesign.components.nodeWorkspace.core.base.NodePosition;
	import net.ncdesign.components.nodeWorkspace.core.interfaces.INodeElement;
	
	[Style(name="gridColor",type="uint",format="Color",inherit="no")]
	[Style(name="linkColor",type="uint",format="Color",inherit="no")]
	
	public class Workspace extends Canvas
	{
		public var nodes:Dictionary = new Dictionary();	
		private var nodeIDArray:Array = [];
		
		public var links:Dictionary = new Dictionary();	
		private var linkIDArray:Array = [];
		
		private var _backgroundColor:uint;
		private var _gridColor:uint;
		private var _linkColor:uint;
		private var _showGrid:Boolean = true;
		
		private var bStypePropChanged:Boolean = true;
		
		private var graphCanvas:UIComponent = new UIComponent();
		private var linkCanvas:Canvas = new Canvas();
		private var nodeCanvas:Canvas = new Canvas();
		
		[Bindable]
		private var relativeHorizontalOffset:Number = 0;
		
		[Bindable]
		private var relativeVerticalOffset:Number = 0;
		
		[Bindable]
		private var _gridSize:int = 20;
		
		[Bindable]
		private var outputNode:INodeElement;
		
		[Bindable]
		private var outputNodeSelected:Boolean = false;
		
		public function set gridSize(value:int):void
		{
			_gridSize = value;
			drawGrid();
		}
		
		[Inspectable(defaultValue="true")] 
		public function set showGrid(value:Boolean):void
		{
			_showGrid = value;
			drawGrid();
		}
		
		public function get relativeCenter():NodePosition
		{
			var posX:int = this.width/2 + relativeHorizontalOffset;
			var posY:int = this.height/2 + relativeVerticalOffset;
			var position:NodePosition = new NodePosition(posX, posY);
			return position
		}
		
		[Bindable]
		public var maskActive:Boolean = true;
		
		[Bindable]
		private var _selectedNode:INodeElement;
		
		public function get selectedNode():INodeElement
		{
			return _selectedNode;
		}
		
		public function set selectedNode(value:INodeElement):void
		{
			_selectedNode = value;
		}
		
		[Bindable]
		private var _locked:Boolean = false;
		
		public function get locked():Boolean
		{
			return _locked;
		}
		
		public function set locked(value:Boolean):void
		{
			_locked = value;
			if ( _locked )
			{
				this.verticalScrollPolicy = ScrollPolicy.OFF;
				this.horizontalScrollPolicy = ScrollPolicy.OFF;
			}
		}
		
		private static var classConstructed:Boolean = classConstruct();

        private static function classConstruct():Boolean {
        	var myWorkspaceStyles:CSSStyleDeclaration = StyleManager.getStyleDeclaration('Workspace');
        	
            if (myWorkspaceStyles)
            {
            	if ( myWorkspaceStyles.getStyle( 'backgroundColor' ) == undefined )
            	{
            		myWorkspaceStyles.setStyle('backgroundColor', 0xFFFFFF);
            	}
            	if ( myWorkspaceStyles.getStyle( 'gridColor' ) == undefined )
            	{
            		myWorkspaceStyles.setStyle('gridColor', 0x99CCFF);
            	}
            	if ( myWorkspaceStyles.getStyle( 'linkColor' ) == undefined )
            	{
            		myWorkspaceStyles.setStyle('linkColor', 0xFFFFFF);
            	}
            } else {
            	myWorkspaceStyles = new CSSStyleDeclaration();
            	myWorkspaceStyles .defaultFactory = function():void
            	{
            		this.backgroundColor = 0xFFFFFF;
            		this.gridColor = 0x99CCFF;
            		this.linkColor = 0xFFFFFF;
            	}
                StyleManager.setStyleDeclaration("Workspace", myWorkspaceStyles, true);
            }
            return true;
        }
        
        override public function styleChanged(styleProp:String):void {

            super.styleChanged(styleProp);

            if (styleProp=="backgroundColor" || styleProp=="gridColor" || styleProp=="linkColor") 
            {
                bStypePropChanged=true; 
                invalidateDisplayList();
                return;
            }
        }
        
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
            super.updateDisplayList(unscaledWidth, unscaledHeight);
			nodeCanvas.width = this.width*2;
			nodeCanvas.height = this.height*2;
			
			graphCanvas.width = nodeCanvas.width;
			graphCanvas.height = nodeCanvas.height;
			
			linkCanvas.width = nodeCanvas.width;
			linkCanvas.height = nodeCanvas.height;
						
            if (bStypePropChanged==true) 
            {
                _backgroundColor = getStyle("backgroundColor");
                _gridColor = getStyle("gridColor");
                
                graphics.beginFill(getStyle("backgroundColor"));
                graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
                graphics.endFill();
                
                bStypePropChanged=false;
            }
            
            if ( maskActive )
            {
	            var maskShape:Shape = new Shape();
	            maskShape.graphics.beginFill(0x000000);
	            maskShape.graphics.drawRect(this.x, this.y, unscaledWidth, unscaledHeight);
	            maskShape.graphics.endFill()
	            
	           this.mask = maskShape;
	        }
            drawGrid();
        }
        
		public function Workspace()
		{
			super();
			addChild(graphCanvas);
			addChild(linkCanvas);
			addChild(nodeCanvas);
			this.horizontalScrollPolicy = ScrollPolicy.ON;
			this.verticalScrollPolicy = ScrollPolicy.ON;
			
			this.addEventListener(ScrollEvent.SCROLL, onScrollHandler);
		}
		
		private function onScrollHandler(event:ScrollEvent):void
		{
			switch ( event.direction )
			{
				case 'horizontal':
					relativeHorizontalOffset += event.delta;
					break;
				case 'vertical':
					relativeVerticalOffset += event.delta;
					break;
			}
		}
		
		private function onLinkInPortSelected(event:Event):void
		{
			var inputNode:INodeElement = event.target as INodeElement;
			if ( outputNodeSelected )
			{
				createLink(inputNode, outputNode);
			}
		}
		
		private function onLinkOutPortSelected(event:Event):void
		{
			outputNodeSelected = !outputNodeSelected;
			if ( outputNodeSelected )
			{
				outputNode = event.target as INodeElement;
			} else {
				outputNode = null;
			}
		}
		
		public function addNode(node:INodeElement):void
		{
			nodes[node.nodeID] = node;
			var nodeComponent:UIComponent = node as UIComponent;
			node.nodePosition.xPos = node.nodePosition.xPos - nodeComponent.width/2;
			node.nodePosition.yPos = node.nodePosition.yPos - nodeComponent.height/2;
			nodeIDArray.push(node.nodeID);
			drawWorkspace();
		}
		
		public function removeNode(node:INodeElement):void
		{
			delete nodes[node.nodeID];
			for ( var i:int = 0; i < nodeIDArray.length; i++ )
			{
				if ( nodeIDArray[i] == node.nodeID )
				{
					nodeIDArray.splice(i, 1);
				}
			}
			drawWorkspace();
		}
		
		public function createLink(inputNode:INodeElement, outputNode:INodeElement):void
		{
			var newLink:NodeLink = new NodeLink(outputNode.nodeID+'.to.'+inputNode.nodeID);
			trace ( "Link " + newLink.name + " Created");
			newLink.nodeIn = inputNode;
			newLink.nodeOut = outputNode;
			links[newLink.name] = newLink;
			linkIDArray.push(newLink.name);
			drawWorkspace();
			outputNodeSelected = false;
			outputNode = null;
		}
		
		private function removeLink(event:Event):void
		{
			delete links[event.target.linkName];
			for ( var i:int = 0; i < linkIDArray.length; i++ )
			{
				if ( linkIDArray[i] == event.target.linkName )
				{
					linkIDArray.splice(i, 1);
				}
			}
			drawLinks();
		}
		
		private function drawWorkspace():void
		{
			nodeCanvas.removeAllChildren();
			for each ( var key:String in nodeIDArray )
			{
				var node:INodeElement = nodes[key] as INodeElement;
				var nodeComponent:UIComponent = node as UIComponent;
				nodeComponent.x = node.nodePosition.xPos;
				nodeComponent.y = node.nodePosition.yPos;
				nodeComponent.addEventListener('linkInPortSelected', onLinkInPortSelected);
				nodeComponent.addEventListener('linkOutPortSelected', onLinkOutPortSelected);
				nodeComponent.addEventListener('nodeStartDrag', onNodeDrag );
				nodeComponent.addEventListener('nodeStopDrag', onNodeStopDrag );
				nodeComponent.addEventListener('nodeSelected', onNodeSelected );
				nodeCanvas.addChild(nodeComponent);
			}
			drawLinks();
		}
		
		public function deselectAll():void
		{
			for each ( var key:String in nodeIDArray )
			{
				var node:INodeElement = nodes[key] as INodeElement;
				node.selected = false;
				selectedNode = null;
			}
		}
		
		private function onNodeSelected(event:Event):void
		{
			for each ( var key:String in nodeIDArray )
			{
				var node:INodeElement = nodes[key] as INodeElement;
				if ( node.nodeID == event.target.nodeID )
				{
					if( !node.selected )
					{
						node.selected = true;
						selectedNode = node;
					} else {
						node.selected = false;
						selectedNode = null;
					}
				} else {
					node.selected = false;
				}
			}
		}
		
		private function onNodeDrag(event:Event):void
		{
			this.addEventListener(Event.ENTER_FRAME, drawLinks);
		}
		
		private function onNodeStopDrag(event:Event):void
		{
			this.removeEventListener(Event.ENTER_FRAME, drawLinks);
		}
		
		private function drawLinks(event:Event = null):void
		{
			var g:Graphics = linkCanvas.graphics;
			linkCanvas.removeAllChildren();
			g.clear();
			for each ( var linkKey:String in linkIDArray )
			{
				var link:NodeLink = links[linkKey] as NodeLink;
				var startPoint:Point, midPoint:Point, endPoint:Point
				
				g.lineStyle(2, getStyle("linkColor"));
				
				var inX:int = link.nodeIn.nodePosition.xPos + link.nodeIn.linkInPort.x + (link.nodeIn.linkInPort.width/2);
				var inY:int = link.nodeIn.nodePosition.yPos +  link.nodeIn.linkInPort.y + (link.nodeIn.linkInPort.height/2); 
				
				var outX:int = link.nodeOut.nodePosition.xPos + link.nodeOut.linkOutPort.x + (link.nodeOut.linkOutPort.width/2);
				var outY:int = link.nodeOut.nodePosition.yPos + link.nodeOut.linkOutPort.y + (link.nodeOut.linkOutPort.height/2);
				
				startPoint = new Point(outX, outY);
				endPoint = new Point(inX, inY);

				midPoint = Point.interpolate(startPoint, endPoint, .5);
				
				g.moveTo(outX,outY);
				g.curveTo(midPoint.x, startPoint.y, midPoint.x, midPoint.y);
				g.curveTo(midPoint.x, endPoint.y, endPoint.x, endPoint.y);
				g.moveTo(midPoint.x, midPoint.y);
				g.beginFill(getStyle("linkColor"), 1);
				g.drawCircle(midPoint.x, midPoint.y, 4);
				g.endFill();
				var _controlPoint:LinkControlPoint = new LinkControlPoint(midPoint, linkKey, getStyle("linkColor"));
				linkCanvas.addChild(_controlPoint);
				_controlPoint.addEventListener('deleteLink', removeLink);
			}
		}
		
		private function drawGrid():void
		{
			var gridGraphics:Graphics = graphCanvas.graphics;
			gridGraphics.clear();
			if ( _showGrid )
			{
				var gridColor:uint = getStyle("gridColor");
				gridGraphics.lineStyle(1, getStyle("gridColor"), .25);
				gridGraphics.moveTo(0,0);
				gridGraphics.lineTo(0, nodeCanvas.height);
				var hDivisions:int = nodeCanvas.width / _gridSize + 1;
				var vDivisions:int = nodeCanvas.height / _gridSize + 1;
				
				for ( var w:int = 0; w < hDivisions; w++ )
				{
					gridGraphics.moveTo(_gridSize * w, 0);
					gridGraphics.lineTo(_gridSize * w, nodeCanvas.height);
				}
				
				gridGraphics.moveTo(0,0);
				gridGraphics.lineTo(nodeCanvas.width, 0);
				
				for ( var h:int = 0; h < vDivisions; h++ )
				{
					gridGraphics.moveTo(0, _gridSize * h);
					gridGraphics.lineTo(nodeCanvas.width, _gridSize * h);
				}
			}
		}
		
		public function refreshWorkspace():void
		{
			drawWorkspace();
		}
		
		public function clearWorkspace():void
		{
			for ( var i:int = 0; i < nodeIDArray.length; i++ )
			{
				delete nodes[nodeIDArray[i]]
			}
			
			for ( var l:int = 0; l < linkIDArray.length; l++ )
			{
				delete links[linkIDArray[l]]
			}
			
			linkIDArray = [];
			nodeIDArray = [];
			refreshWorkspace();
		}
		
		public function getNodes():ArrayCollection
		{
			var nodeArray:ArrayCollection = new ArrayCollection();
			for ( var i:int = 0; i < nodeIDArray.length; i++ )
			{
				var node:INodeElement = nodes[nodeIDArray[i]] as INodeElement;
				nodeArray.addItem(node);
			}
			return nodeArray
		}
		
	}
}